SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
3.9 KB · 79 lines tsx
Raw Blame History
1import Link from 'next/link';2import { notFound } from 'next/navigation';3import { Section, KV } from '@/components/ui/section';4import { Badge, StatusBadge } from '@/components/ui/badge';5import { JsonView } from '@/components/ui/json-view';6import { getRun } from '@/lib/queries/sources';7import { fmtDateTime, fmtDuration, fmtInt } from '@/lib/format';89export default async function RunPage({ params }: { params: Promise<{ runId: string }> }) {10  const { runId } = await params;11  const r = await getRun(runId);12  if (!r) notFound();13  return (14    <div className="space-y-8">15      <header className="pt-4">16        <p className="ci-kicker">Ingest run</p>17        <h1 className="ci-mono text-2xl font-sans">{r.id}</h1>18        <p className="mt-1 flex flex-wrap items-center gap-2 text-[13px]">19          <Link className="ci-link" href={`/source/${r.source_slug}`}>20            {r.source_name ?? r.connector_id}21          </Link>22          <StatusBadge status={r.status} />23          <Badge tone="outline">{r.mode}</Badge>24          {r.anomaly ? <Badge tone="danger">anomaly: {r.anomaly}</Badge> : null}25        </p>26      </header>27      <div className="grid gap-8 lg:grid-cols-[1fr_1fr]">28        <Section id="counts" kicker="Counts" title="Records and requests">29          <KV30            items={[31              { k: 'Started', v: fmtDateTime(r.started_at) },32              { k: 'Finished', v: r.finished_at ? fmtDateTime(r.finished_at) : 'running' },33              { k: 'Duration', v: fmtDuration(r.duration_ms) },34              { k: 'Fetched', v: <span className="ci-num">{fmtInt(r.records_fetched)}</span> },35              { k: 'Created', v: <span className="ci-num">{fmtInt(r.records_created)}</span> },36              { k: 'Updated', v: <span className="ci-num">{fmtInt(r.records_updated)}</span> },37              { k: 'Unchanged', v: <span className="ci-num">{fmtInt(r.records_unchanged)}</span> },38              { k: 'Rejected', v: <span className="ci-num">{fmtInt(r.records_rejected)}</span> },39              { k: 'Validation failures', v: <span className="ci-num">{fmtInt(r.validation_failures)}</span> },40              { k: 'HTTP requests / failures', v: <span className="ci-num">{fmtInt(r.http_requests)} / {fmtInt(r.http_failures)}</span> },41              { k: 'Rate-limit events', v: <span className="ci-num">{fmtInt(r.rate_limit_events)}</span> },42              { k: 'Dataset version', v: r.dataset_version ? <span className="ci-mono">{r.dataset_version}</span> : null },43              { k: 'Error', v: r.error ? <span className="text-danger">{r.error}</span> : null },44            ]}45          />46        </Section>47        <Section id="cursor" kicker="Restartability" title="Cursor before / after">48          <div className="grid gap-4 sm:grid-cols-2">49            <div>50              <p className="ci-kicker mb-1">Before</p>51              <JsonView data={r.cursor_before} />52            </div>53            <div>54              <p className="ci-kicker mb-1">After</p>55              <JsonView data={r.cursor_after} />56            </div>57          </div>58        </Section>59      </div>60      <Section id="drift" kicker="Schema drift" title={`Drift signals (${r.schema_drift?.length ?? 0})`}>61        {r.schema_drift?.length ? <JsonView data={r.schema_drift} /> : <p className="text-[13px] text-ink-3">No schema drift detected in this run.</p>}62      </Section>63      <Section id="log" kicker="Log" title={`Log (${r.log?.length ?? 0} lines)`}>64        {r.log?.length ? (65          <pre className="ci-code max-h-[600px] overflow-auto">66            {r.log.map((l, i) => (67              <div key={i} className={l.level === 'error' ? 'text-danger' : l.level === 'warn' ? 'text-warn' : ''}>68                <span className="text-ink-3">{l.t}</span> <span className="uppercase">{l.level.padEnd(5)}</span> {l.msg}69              </div>70            ))}71          </pre>72        ) : (73          <p className="text-[13px] text-ink-3">Empty log.</p>74        )}75      </Section>76    </div>77  );78}79